package com.jctf.net;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SequenceServer3 {

	private final static int PORT = 33962;
	private final static int MAX_CLIENTS = 10;
	private final static Level LOGGER_LEVEL = Level.INFO;

	private static Logger logger = Logger.getLogger("com.jctf.net.SequenceServer3");
	private static FileHandler fh;

	private HashMap<String, String> loginData = new HashMap<String, String>();
	private HashMap<String, PlayerData> players = new HashMap<String, PlayerData>();
	private NetworkManager manager;
	private MessageDigest encryptor;

	public static void main(String[] args) {
		try {
			fh =  new FileHandler("log.xml");
			logger.addHandler(fh);
			logger.setLevel(LOGGER_LEVEL);
		} catch (SecurityException e) {
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		new SequenceServer3().run();
	}

	public void run() {
		System.out.print("Setting up login data...");
		readLoginData();
		try {
			encryptor = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e1) {
			logger.log(Level.SEVERE, "Encryptor initialization failed", e1);
			System.exit(1);
		}
		System.out.println("done.");
		System.out.print("Setting up server...");
		try {
			initServer();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("Quitting.");
		fh.close();
	}

	private void readLoginData() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(
					SequenceServer3.class.getResource("data/logindata.txt")
					.toURI())));
			ArrayList<String> sets = new ArrayList<String>();
			String temp = reader.readLine();
			while (temp != null) {
				sets.add(temp);
				temp = reader.readLine();
			}
			for (String s : sets) {
				String[] a = s.split(" ");
				loginData.put(a[0], a[1]);
			}
		} catch (FileNotFoundException e) {
			logger.log(Level.SEVERE, "Could not find login data", e);
			System.exit(1);
		} catch (IOException e) {
			logger.log(Level.SEVERE, "I/O error reading login data", e);
			System.exit(1);
		} catch (URISyntaxException e) {
			logger.log(Level.SEVERE, "Login data URI syntax not recognized", e);
			System.exit(1);
		}
	}

	private void initServer() throws IOException {
		manager = new NetworkManager();
		System.out.println("done.");
		System.out.println("Waiting for connections...");
		manager.start();
	}

	private class NetworkManager {

		private ServerSocket server;
		private ArrayList<Socket> connections = new ArrayList<Socket>();
		private ArrayList<ObjectInputStream> inputs = new ArrayList<ObjectInputStream>();
		private ArrayList<ObjectOutputStream> outputs = new ArrayList<ObjectOutputStream>();
		private int nextId = 0;

		public void start() throws IOException {
			server = new ServerSocket(PORT, MAX_CLIENTS);
			new Hermes().start();
			acceptNewConnections();
		}

		private void acceptNewConnections() throws IOException {
			while (true) {
				Socket connection = server.accept();
				synchronized (connection) {
					connections.add(connection);
					inputs.add(new ObjectInputStream(connection
							.getInputStream()));
					outputs.add(new ObjectOutputStream(connection
							.getOutputStream()));
					System.out.println("Received connection from "
							+ connection.getInetAddress().getHostAddress());
				}
			}
		}

		private class Hermes extends Thread {

			@Override
			public void run() {
				setName("Hermes");
				while (true) {
					for (String user : players.keySet()) {
						try {
							PlayerData player = players.get(user);
							// Socket c = player.getConnection();
							// ObjectOutputStream out = player.getOutput();
							ObjectInputStream in = player.getInput();
							Object message = in.readObject();
							if (message == null)
								continue;
							else if (message instanceof NumberMessage)
								System.out
								.println("Client "
										+ user
										+ ": "
										+ ((NumberMessage) message)
										.getNumber());
						} catch (EOFException e) {
							System.out.println("Client " + user + " exit.");
							players.remove(user);
						} catch (IOException e) {
							logger.log(Level.SEVERE,
									"I/O error communicating with clients", e);
							System.exit(1);
						} catch (ClassNotFoundException e) {
							logger
							.log(
									Level.SEVERE,
									"Error casting during communication deserialization",
									e);
							System.exit(1);
						}
					}
					for (int i = 0; i < connections.size(); i++) {
						try {
							Socket c = connections.get(i);
							if (inputs.size() == 0 || outputs.size() == 0
									|| connections.size() == 0)
								continue;
							ObjectInputStream in = inputs.get(i);
							ObjectOutputStream out = outputs.get(i);
							Object message = inputs.get(i).readObject();
							if (message == null)
								continue;
							else if (message instanceof RequestLoginMessage) {
								RequestLoginMessage m = (RequestLoginMessage) message;
								System.out
								.println("Login request received from "
										+ c.getInetAddress()
										.getHostAddress() + "/"
										+ m.getUsername());
								String usr = m.getUsername();
								if (players.get(usr) != null) {
									logger.log(Level.WARNING, "Client " + usr
											+ " attempted multiple logins.");
									out.writeObject(new DenyLoginMessage());
									out.flush();
									connections.remove(i);
									outputs.remove(i);
									inputs.remove(i);
									i--;
									continue;
								}
								String toHash = m.getUsername() + " "
								+ m.getPassword();
								encryptor.update(toHash.getBytes(), 0, toHash
										.length());
								String hash = new BigInteger(1, encryptor
										.digest()).toString(16);
								if (hash.equals(loginData.get(m.getUsername()))) {
									players.put(m.getUsername(),
											new PlayerData(c, in, out));
									System.out
									.println("Authentication complete - client "
											+ usr
											+ " logged in. Responding...");
									nextId++;
									out.writeObject(new ConfirmLoginMessage());
									out.flush();
									connections.remove(i);
									outputs.remove(i);
									inputs.remove(i);
									i--;
									continue;
								} else {
									logger.log(Level.INFO,
											"Authentication failed with client "
											+ c.getInetAddress()
											.getHostAddress()
											+ "/" + m.getUsername()
											+ ".");
									out.writeObject(new DenyLoginMessage());
									out.flush();
									connections.remove(i);
									outputs.remove(i);
									inputs.remove(i);
									i--;
								}
							}
						} catch (IOException e) {
							logger.log(Level.SEVERE,
									"I/O error parsing login request", e);
						} catch (ClassNotFoundException e) {
							logger
							.log(
									Level.SEVERE,
									"Error casting during login deserialization",
									e);
						}
					}
				}
			}
		}
	}
}